Skip to content

Propagate deprecation metadata from DTO source elements into generated builders (#235) - #253

Merged
AndreasIgel merged 17 commits into
mainfrom
feature/issue-235-deprecated-annotation-propagation
Aug 16, 2026
Merged

Propagate deprecation metadata from DTO source elements into generated builders (#235)#253
AndreasIgel merged 17 commits into
mainfrom
feature/issue-235-deprecated-annotation-propagation

Conversation

@AndreasIgel

@AndreasIgel AndreasIgel commented Aug 16, 2026

Copy link
Copy Markdown
Collaborator

Description

Resolves #235

Problem

Generated builders did not carry @Deprecated annotations or @deprecated Javadoc from the source DTO elements. Consumers of the generated builder APIs had no indication that a property or DTO class was deprecated, and the generated implementation code produced unchecked deprecation warnings when internally calling deprecated DTO members.

Solution

This PR propagates deprecation metadata through the full DTO-to-builder generation pipeline:

Deprecation propagation to generated builder methods:

  • @Deprecated on constructor parameters, record components, setter methods, and field types is detected and propagated to all generated builder methods for that property (fluent setter, supplier, consumer, varargs, string-format, collection helpers, With interface).
  • since and forRemoval attributes are preserved.
  • @deprecated Javadoc text is extracted from the enclosing executable (setter/constructor) or record component and added to the generated method Javadoc.

Deprecation propagation from DTO class:

  • @Deprecated on the DTO class propagates to the generated builder class itself, both constructors, and the create() factory method.

Class-level suppression:

  • The generated builder class receives @SuppressWarnings({"deprecation", "removal"}) when any internal call touches a deprecated member:
    • Deprecated DTO class
    • Deprecated selected constructor
    • Deprecated getter called by the from-instance constructor
    • Deprecated builder methods (e.g. from deprecated setter/parameter/record component/field type) called by other generated helpers
    • Deprecated collection element types — the generated collection helper calls the element builder's create() internally

Design decisions:

  • Getter deprecation is not a propagation source — the builder exposes no get-API; only the from-instance constructor calls the getter internally, covered by class-level suppression. The getter deprecation flag is stored in GetterInfoDto during field creation and checked separately.
  • Setter deprecation is a propagation source — the builder's fluent methods replace the setter as the write API for the property. Setter deprecation is covered by the general anyMethodDeprecated check since it propagates @Deprecated to builder methods.
  • Backing field deprecation is not checked — it is uncommon to deprecate a field without also deprecating the setter or constructor parameter, and checking the field adds complexity without practical value.
  • @Deprecated is not copied to builder method parameters — the parameter is a new declaration; deprecation is about the property, not the parameter value. The annotation is applied to the builder method itself via DeprecationInfoDto.

Changes

Implementation:

  • DeprecationInfoDto — new DTO carrying the @Deprecated annotation (with since/forRemoval) and @deprecated Javadoc text.
  • GetterInfoDto — new record consolidating getter name and deprecation flag, set during field creation. Enforces non-null getter name via compact constructor.
  • FieldDto — nullable deprecationInfo field; isDeprecated() derives from deprecatedAnnotation != null. getterInfo field replaces separate getterName/getterDeprecated fields; convenience methods getGetterName() and isGetterDeprecated() delegate to it.
  • FieldAnnotationExtractorextractDeprecatedAnnotation(Element, ProcessingContext) detects @Deprecated on any element without applying annotation filters. @Deprecated added to ANNOTATION_FILTERS so it is no longer copied to builder parameters. Split extractAnnotation into filtering and non-filtering variants.
  • BuilderDefinitionCreatordetectAndApplyFieldDeprecation(...) detects deprecation from parameter -> record component -> setter -> field type (getter and backing field excluded). applyDeprecationToMethod(...) applies @Deprecated and Javadoc to generated methods. applyDeprecationSuppressions(...) adds class-level @SuppressWarnings based on internal deprecated calls, using DEPRECATED_TYPE = map2TypeName(Deprecated.class) for type-safe TypeName comparison.
  • JavaLangAnalyser — added findFieldElement(...) (using ElementFilter.fieldsIn(...)), findRecordComponent(...), and extractDeprecatedJavaDoc(...) helpers.
  • RoasterCodeGenerator — renders @Deprecated and @SuppressWarnings via applyAnnotations with simple-name handling for java.lang annotations.
  • JavadocDto — added addDeprecated(String) for @deprecated tag support.

Tests:

  • AnnotationCopyTest — 22 tests covering: deprecated constructor parameter, deprecated setter, deprecated record component, deprecated DTO class (full text-block comparison with minimal options), deprecated Javadoc propagation, since/forRemoval attribute preservation, deprecated getter (no method-level propagation, class-level suppression only, getter call in from-instance constructor), deprecated field type (method-level deprecation + class suppression), deprecated element builder (class-level suppression for collection helper calling deprecated element builder), and framework annotation filtering.

AndreasIgel and others added 12 commits August 15, 2026 10:57
The class-level Javadoc and docs/CONFIGURATION.md implied that
@SimpleBuilder is inherited by subclasses, but the annotation was not
meta-annotated with @inherited. As a result BuilderProcessor, which
collects types via RoundEnvironment.getElementsAnnotatedWith(...),
only produced builders for the exact type carrying @SimpleBuilder and
not for unannotated subclasses.

Add @inherited to @SimpleBuilder so subclasses are treated as if they
also carried the annotation, mirroring the existing behaviour of
@SimpleBuilder.Template (which is already @inherited). Update the
Javadoc to document the inheritance explicitly and clarify the
CONFIGURATION.md wording. @Ignore4BuilderGeneration still suppresses
generation for the exact type it is placed on, so opt-outs continue
to work as before.

Add SimpleBuilderInheritanceTest covering direct inheritance, the
opt-out interaction, and multi-level (grandchild) inheritance.

Closes #244

Generated with [Devin](https://devin.ai)

Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
…ance

Rename SimpleBuilderInheritanceTest to BuilderAnnotationInheritanceTest
so the name reflects that it covers both builder-triggering annotations.
Add unannotatedSubclassGetsBuilderFromInheritedTemplate, which verifies
that a custom @inherited template annotation (meta-annotated with
@SimpleBuilder.Template) propagates to unannotated subclasses, matching
the existing behaviour of @SimpleBuilder itself.

Generated with [Devin](https://devin.ai)

Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
The Template Javadoc and CONFIGURATION.md "Template Annotations" section
did not explain that @SimpleBuilder.Template is @inherited, nor that a
custom template annotation must additionally declare @inherited to
propagate to unannotated subclasses. Add explicit documentation and an
example showing the @inherited custom annotation pattern.

Also move assertNoBuilderGenerated to ProcessorAsserts so it is shared
by BuilderAnnotationInheritanceTest and Ignore4BuilderGenerationTest
instead of being duplicated as a private helper in each test class.

Generated with [Devin](https://devin.ai)

Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
While @SimpleBuilder and @inherited template annotations now correctly
trigger builder generation for unannotated subclasses, the configuration
options declared on the parent's @SimpleBuilder(options = ...) or template
are not yet applied to inherited subclass builders — they use default
options instead. This is tracked separately in issue #245.

Add caveats to the SimpleBuilder Javadoc, the CONFIGURATION.md Template
Annotations section, and the Template Annotations Not Working
troubleshooting section so users are not surprised by this limitation.

Generated with [Devin](https://devin.ai)

Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Generated with [Devin](https://devin.ai)

Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
@AndreasIgel
AndreasIgel force-pushed the feature/issue-235-deprecated-annotation-propagation branch from d1d0f12 to 47423e0 Compare August 16, 2026 18:21
…ation

Resolved Javadoc conflict in SimpleBuilder.Template by taking the more
complete documentation from main (ANNOTATION_TYPE restriction note and
issue #248 reference).

Generated with [Devin](https://devin.ai)

Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
@codecov

codecov Bot commented Aug 16, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 91.89189% with 15 lines in your changes missing coverage. Please review.
✅ All tests successful. No failed tests found.

Files with missing lines Patch % Lines
.../builders/processor/analysis/JavaLangAnalyser.java 85.36% 1 Missing and 5 partials ⚠️
...s/processor/analysis/FieldAnnotationExtractor.java 66.66% 2 Missing and 3 partials ⚠️
...processor/processing/BuilderDefinitionCreator.java 96.63% 0 Missing and 4 partials ⚠️

📢 Thoughts on this report? Let us know!

AndreasIgel and others added 4 commits August 16, 2026 20:52
…ation

Resolved conflict in SimpleBuilder.java: took updated Javadoc from
origin/main (PR #252) which now documents that template options ARE
applied to inherited subclass builders, replacing the old note about
issue #248.

Generated with [Devin](https://devin.ai)

Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
@sonarqubecloud

Copy link
Copy Markdown

@AndreasIgel
AndreasIgel merged commit 34177b3 into main Aug 16, 2026
8 checks passed
@AndreasIgel
AndreasIgel deleted the feature/issue-235-deprecated-annotation-propagation branch August 16, 2026 20:45
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Propagate @Deprecated (and its javadoc) from DTO, setters and constructor parameters to the generated builder

1 participant